package utils

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"runtime"
	"runtime/debug"
	"strings"
	"sync/atomic"
	"time"
)

var __id int32 = 0
var GoFunCatchException bool = true

var (
	panicLogFilePath string                                                    = "log" + PathSeparator
	OnPanicEvent     func(err interface{}, args ...interface{}) (isBreak bool) = nil
)

type (
	GoFuncType  func(data ...interface{})
	GoFunc2Type func()
)

func SetPanicLogFilePath(path string) {
	path = Trim(path)
	if len(path) == 0 {
		return
	}
	if !strings.HasSuffix(path, PathSeparator) {
		path += PathSeparator
	}
	ForceCreatePath(path)
	ok, err := PathExists(path)
	if err != nil {
		fmt.Fprintf(os.Stderr, "[SetPanicLogFilePath] %s err:%s???", path, err.Error())
		return
	}
	if !ok {
		fmt.Fprintf(os.Stderr, "[SetPanicLogFilePath] %s is not exists!!!", path)
		return
	}
	panicLogFilePath = path

}

func GetCallStack(callstackskip int) string {
	src := string(debug.Stack())
	return src
}

func GetCallStackLine(callstackskip int) string {
	pc, _, lineno, ok := runtime.Caller(callstackskip + 1)
	src := ""
	if ok {
		src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno)
	}
	return src
}

func GoFunc(cb GoFuncType, params ...interface{}) {
	if GoFunCatchException {
		go func() {
			defer PanicHandler()

			cb(params...)
		}()
	} else {
		go cb(params...)
	}
}

func GoFunc2(cb GoFunc2Type) {
	if GoFunCatchException {
		go func() {
			defer PanicHandler()
			cb()
		}()
	} else {
		go cb()
	}
}

var (
	fatalFile        *os.File = nil
	cmdflag_dumpfile *string  = nil
	org_fatal_out    *os.File = nil // 记录最原始的, 便于还原
)

func CmdFlagPrepareDumpFile() {
	cmdflag_dumpfile = flag.String("dumpfile", "", "")
}

func DeferFatalRec() {
	if fatalFile != nil {
		fatalFile.Close()
		fatalFile = nil
		err := RedirectStderr(org_fatal_out, "DeferFatalRec")
		if err != nil {
			fmt.Fprintf(os.Stderr, "[%s]DeferFatalRec>>RedirectStderr err:%s", NowString(), err.Error())
		}
	}
}

func GetRecFatalFile(strPath string) string {
	pid := os.Getpid()                        //获取进程ID
	time_str := time.Now().Format("20060102") //设定时间格式
	exeName := os.Args[0]                     //获取程序名称
	exeName = ExtractFileNameWithOutPathAndExt(exeName)

	recfile := fmt.Sprintf("%s%s-%d-%s-fatal-dump.log", strPath, exeName, pid, time_str) //保存错误信息文件名:程序名-进程ID-当前时间（年月日时分秒）
	return recfile
}

func SetFatalRec(recfile string) error {
	if fatalFile != nil {
		return errors.New("已经开启！")
	}

	if org_fatal_out == nil {
		org_fatal_out = os.Stderr
	}

	if cmdflag_dumpfile != nil {
		if len(*cmdflag_dumpfile) == 0 {
			return errors.New("未配置dumpfile")
		}
		recfile = *cmdflag_dumpfile

		if len(recfile) == 0 {
			return nil
		}
	}

	if len(recfile) == 0 {
		ForceCreatePath(panicLogFilePath)
		recfile = GetRecFatalFile(panicLogFilePath)
	}

	_logFile, err := os.OpenFile(recfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660)
	if err != nil {
		return err
	}
	fatalFile = _logFile
	err = RedirectStderr(fatalFile, "SetFatalRec:"+recfile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "[%s]SetFatalRec>>RedirectStderr err:%s", NowString(), err.Error())
	}
	return err
}

func PanicHandler2NULL() {
	if err := recover(); err != nil {

	}
}

func PanicHandlerWithPrint() {

	if err := recover(); err != nil {
		if OnPanicEvent != nil {
			if OnPanicEvent(err) {
				return
			}

		}
		fmt.Println(fmt.Sprintf("err:%v, stack:%v", err, debug.Stack()))
	}
}

func PanicHandlerCallBack(fn func(paincErr interface{})) {
	if err := recover(); err != nil {
		fn(err)
	}
}

func PanicHandlerWithDebugString(debuginfo string) {
	if err := recover(); err != nil {
		if OnPanicEvent != nil {
			if OnPanicEvent(err, debuginfo) {
				return
			}

		}

		exeName := os.Args[0] //获取程序名称
		exeName = ExtractFileNameWithOutPathAndExt(exeName)

		now := time.Now()  //获取当前时间
		pid := os.Getpid() //获取进程ID

		uid := atomic.AddInt32(&__id, 1)

		ForceCreatePath(panicLogFilePath)

		time_str := now.Format("20060102")                                                            //设定时间格式
		fname := fmt.Sprintf("%s%s-%d-%d-%s-dump.log", panicLogFilePath, exeName, pid, uid, time_str) //保存错误信息文件名:程序名-进程ID-当前时间（年月日时分秒）

		f, err2 := os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
		if err2 != nil {
			fmt.Println("存储异常文件失败:", err2)
			fmt.Println("捕捉到的异常:", err)
			fmt.Println("异常堆栈信息:", string(debug.Stack())) //输出堆栈信息
			fmt.Println("数据:", debuginfo)
			return
		}
		defer f.Close()
		fmt.Println("存储异常到文件: ", fname)
		f.WriteString(fmt.Sprintf("%v\r\n", err)) //输出panic信息
		f.WriteString("========\r\n")
		fmt.Println("has error")
		fmt.Println(err)
		fmt.Println("debug stack output:")
		f.WriteString(string(debug.Stack())) //输出堆栈信息
		f.WriteString("\n数据:" + debuginfo)   //调试数据
	}
}

func PanicHandlerWithDebugBuf(str string, debuginfo []byte) {
	if err := recover(); err != nil {
		strDebugHex := BufToHexStr(debuginfo, 0, " ")
		if OnPanicEvent != nil {
			if OnPanicEvent(err, str, strDebugHex) {
				return
			}

		}

		exeName := os.Args[0] //获取程序名称
		exeName = ExtractFileNameWithOutPathAndExt(exeName)

		now := time.Now()  //获取当前时间
		pid := os.Getpid() //获取进程ID

		uid := atomic.AddInt32(&__id, 1)

		ForceCreatePath("log")

		time_str := now.Format("20060102")                                                            //设定时间格式
		fname := fmt.Sprintf("%s%s-%d-%d-%s-dump.log", panicLogFilePath, exeName, pid, uid, time_str) //保存错误信息文件名:程序名-进程ID-当前时间（年月日时分秒）

		f, err2 := os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
		if err2 != nil {
			fmt.Println("存储异常文件失败:", err2)
			fmt.Println("捕捉到的异常:", err)
			fmt.Println("异常堆栈信息:", string(debug.Stack())) //输出堆栈信息
			fmt.Println("信息:", str)
			fmt.Println("数据:", strDebugHex)
			return
		}
		defer f.Close()
		fmt.Println("存储异常到文件: ", fname)
		f.WriteString(fmt.Sprintf("[%s]%v\r\n", NowString(), err)) //输出panic信息
		f.WriteString("========\r\n")
		strCallStack := string(debug.Stack())
		fmt.Println("has error")
		fmt.Println(err)
		fmt.Println("debug stack output:")
		fmt.Println(strCallStack)
		f.WriteString(strCallStack) //输出堆栈信息
		f.WriteString("\n信息:" + str)
		f.WriteString("\n数据:" + strDebugHex) //调试数据
	}
}

func TryCatchPanic(cb func(err interface{}, callstack string)) {
	if err := recover(); err != nil {
		strCallStack := string(debug.Stack())
		cb(err, strCallStack)
	}
}

func PanicHandlerWithCallbackDebugInfo(fn func() string) {
	if err := recover(); err != nil {
		str := fn()
		if OnPanicEvent != nil {
			if OnPanicEvent(err, str) {
				return
			}
		}

		exeName := os.Args[0] //获取程序名称
		exeName = ExtractFileNameWithOutPathAndExt(exeName)

		now := time.Now()  //获取当前时间
		pid := os.Getpid() //获取进程ID

		uid := atomic.AddInt32(&__id, 1)

		ForceCreatePath("log")

		time_str := now.Format("20060102")                                                            //设定时间格式
		fname := fmt.Sprintf("%s%s-%d-%d-%s-dump.log", panicLogFilePath, exeName, pid, uid, time_str) //保存错误信息文件名:程序名-进程ID-当前时间（年月日时分秒）
		strCallStack := string(debug.Stack())

		f, err2 := os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
		if err2 != nil {
			fmt.Println("存储异常文件失败:", err2)
			fmt.Println("捕捉到的异常:", err)
			fmt.Println("异常堆栈信息:", string(debug.Stack())) //输出堆栈信息
			fmt.Println("信息:", str)
			return
		}
		defer f.Close()
		fmt.Println("存储异常到文件: ", fname)
		f.WriteString(fmt.Sprintf("[%s]%v\r\n", NowString(), err)) //输出panic信息
		f.WriteString("========\r\n")
		fmt.Println("has error")
		fmt.Println(err)
		fmt.Println("debug stack output:")
		fmt.Println(strCallStack)
		f.WriteString(strCallStack) //输出堆栈信息
		f.WriteString("\n信息:" + str)

	}
}

func PanicHandlerWithCallBackBuf(str string, fn func() []byte) {
	if err := recover(); err != nil {
		strDebugHex := BufToHexStr(fn(), 0, "")
		if OnPanicEvent != nil {
			if OnPanicEvent(err, str, strDebugHex) {
				return
			}
		}

		exeName := os.Args[0] //获取程序名称
		exeName = ExtractFileNameWithOutPathAndExt(exeName)

		now := time.Now()  //获取当前时间
		pid := os.Getpid() //获取进程ID

		uid := atomic.AddInt32(&__id, 1)

		ForceCreatePath("log")

		time_str := now.Format("20060102")                                                            //设定时间格式
		fname := fmt.Sprintf("%s%s-%d-%d-%s-dump.log", panicLogFilePath, exeName, pid, uid, time_str) //保存错误信息文件名:程序名-进程ID-当前时间（年月日时分秒）
		strCallStack := string(debug.Stack())

		f, err2 := os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
		if err2 != nil {
			fmt.Println("存储异常文件失败:", err2)
			fmt.Println("捕捉到的异常:", err)
			fmt.Println("异常堆栈信息:", string(debug.Stack())) //输出堆栈信息
			fmt.Println("信息:", str)
			fmt.Println("数据:", strDebugHex)
			return
		}
		defer f.Close()
		fmt.Println("存储异常到文件: ", fname)
		f.WriteString(fmt.Sprintf("[%s]%v\r\n", NowString(), err)) //输出panic信息
		f.WriteString("========\r\n")
		fmt.Println("has error")
		fmt.Println(err)
		fmt.Println("debug stack output:")
		fmt.Println(strCallStack)
		f.WriteString(strCallStack) //输出堆栈信息
		f.WriteString("\n信息:" + str)
		f.WriteString("\n数据:" + strDebugHex) //调试数据

	}
}

func PanicHandler() {
	if err := recover(); err != nil {
		if OnPanicEvent != nil {
			if OnPanicEvent(err) {
				return
			}
		}
		exeName := os.Args[0] //获取程序名称
		exeName = ExtractFileNameWithOutPathAndExt(exeName)

		now := time.Now()  //获取当前时间
		pid := os.Getpid() //获取进程ID

		uid := atomic.AddInt32(&__id, 1)

		ForceCreatePath(panicLogFilePath)

		time_str := now.Format("20060102")                                                            //设定时间格式
		fname := fmt.Sprintf("%s%s-%d-%d-%s-dump.log", panicLogFilePath, exeName, pid, uid, time_str) //保存错误信息文件名:程序名-进程ID-当前时间（年月日时分秒）

		f, err2 := os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
		if err2 != nil {
			fmt.Fprintln(os.Stderr, "存储异常文件失败:", err2)
			fmt.Fprintln(os.Stderr, "捕捉到的异常:", err)
			fmt.Fprintln(os.Stderr, "异常堆栈信息:", string(debug.Stack())) //输出堆栈信息
			return
		}
		defer f.Close()
		fmt.Println("存储异常到文件: ", fname)
		f.WriteString(fmt.Sprintf("[%s]:%v\r\n", NowString(), err)) //输出panic信息
		f.WriteString("========\r\n")
		f.WriteString(string(debug.Stack())) //输出堆栈信息

		fmt.Println("has error")
		fmt.Println(err)
		fmt.Println("debug stack output:")

		fmt.Println(string(debug.Stack())) //输出堆栈信息
	}
}

func PanicHandlerTest() {
	exeName := os.Args[0] //获取程序名称
	exeName = ExtractFileNameWithOutPathAndExt(exeName)

	now := time.Now()  //获取当前时间
	pid := os.Getpid() //获取进程ID

	time_str := now.Format("20060102150405")                                              //设定时间格式
	fname := fmt.Sprintf("%s%s-%d-%s-dump.log", panicLogFilePath, exeName, pid, time_str) //保存错误信息文件名:程序名-进程ID-当前时间（年月日时分秒）

	f, err2 := os.Create(fname)
	if err2 != nil {
		fmt.Println("存储异常文件失败:", err2)
		fmt.Println("异常堆栈信息:", string(debug.Stack())) //输出堆栈信息
		return
	}
	defer f.Close()
	fmt.Println("存储异常到文件: ", fname)
	f.WriteString("========\r\n")
	fmt.Println("has error")
	fmt.Println("debug stack output:")
	f.WriteString(string(debug.Stack())) //输出堆栈信息

}

func PanicHandlerTest2() {
	exeName := os.Args[0] //获取程序名称
	exeName = ExtractFileNameWithOutPathAndExt(exeName)

	now := time.Now()  //获取当前时间
	pid := os.Getpid() //获取进程ID

	time_str := now.Format("20060102150405")                                              //设定时间格式
	fname := fmt.Sprintf("%s%s-%d-%s-dump.log", panicLogFilePath, exeName, pid, time_str) //保存错误信息文件名:程序名-进程ID-当前时间（年月日时分秒）

	f, err2 := os.Create(fname)
	if err2 != nil {
		fmt.Println("存储异常文件失败:", err2)
		fmt.Println("异常堆栈信息:", string(debug.Stack())) //输出堆栈信息
		return
	}
	defer f.Close()
	fmt.Println("存储异常到文件: ", fname)
	f.WriteString("========\r\n")
	fmt.Println("has error")
	fmt.Println("debug stack output:")
	f.WriteString(string(debug.Stack())) //输出堆栈信息

}
